﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Sustainalytics.Controversies.BusinessLogic;
using Sustainalytics.Entities;
using Sustainalytics.Utils;

namespace Sustainalytics.Accounts.BusinessLogic
{
    public class IpDataCheck:IIpDataCheck
    {
        private IPlainBl<AccountIpAuthentication> _plainBl;
        private IPlainBl<User> _plainBlUser;


        private IpDataCheck() { }

        public IpDataCheck(IPlainBl<AccountIpAuthentication> plainBl, IPlainBl<User> plainBlUser)
        {
            _plainBl = plainBl;
            _plainBlUser = plainBlUser;
        }

 
        public static IIpDataCheck GetInstance(IPlainBl<AccountIpAuthentication> plainBl, IPlainBl<User> plainBlUser)
        {
            return new IpDataCheck
            {
                _plainBl = plainBl,
                _plainBlUser = plainBlUser

            };
        }
        

        public AccountIpAuthentication[] GetIpAuthenticationIntersections(AccountIpAuthentication auth)
        {

            for (int i = 0; i < auth.IpDatas.Count; i++)
            {
                var currentIndex = i;
                var ip = auth.IpDatas[i];
                for (int j = 0; j < auth.IpDatas.Count; j++)
                {
                    if (currentIndex == j) continue;
                    var currentVal = auth.IpDatas[j];
                    //check if it is range
                    if (string.IsNullOrEmpty(ip.IpTo))
                    {
                        if (string.IsNullOrEmpty(currentVal.IpTo))
                        {
                            if (ip.IpFrom.Equals(currentVal.IpFrom)) //both ip are ip only
                            {
                                return new[] {auth};
                            }
                        }
                        else //currentVal is range
                        {
                            var ipRange=new IpRangeFinder(IPAddress.Parse(currentVal.IpFrom),IPAddress.Parse(currentVal.IpTo));
                            if (ipRange.InRange(IPAddress.Parse(ip.IpFrom)))
                            {
                                return new[] {auth};
                            }
                        }
                    }
                    else // ip to is a range
                    {
                        var range = new IpRangeFinder(IPAddress.Parse(ip.IpFrom), IPAddress.Parse(ip.IpTo));
                        if (string.IsNullOrEmpty(currentVal.IpTo))
                        {
                            if (range.InRange(IPAddress.Parse(currentVal.IpFrom)))
                            {
                                return new[] {auth};
                            }
                        }
                        else
                        {
                            if (range.Intersects(IPAddress.Parse(currentVal.IpFrom), IPAddress.Parse(currentVal.IpTo)))
                            {
                                return new[] {auth};
                            }
                        }
                    }
                }
            }



            var activeAccountData = _plainBl.ReadAllWhere(a =>a.Id!=auth.Id && a.Status == Status.Active && a.IpDatas.Any());
            var activeAccountRange = activeAccountData.Select(a => new 
            {
                a.Id,
                RangeFinders =
                    a.IpDatas.Where(c => !string.IsNullOrEmpty(c.IpTo))
                        .Select(d => new IpRangeFinder(IPAddress.Parse(d.IpFrom), IPAddress.Parse(d.IpTo))).ToList()
            }).Where(a=>a.RangeFinders.Any()).ToList();
            var onlyIpData = activeAccountData.Select(d => new
            {
                d.Id,
                Ip = d.IpDatas.Where(a => !string.IsNullOrEmpty(a.IpFrom) && string.IsNullOrEmpty(a.IpTo)).Select(a=>a.IpFrom).ToList()
            }).ToList();
            var list = new List<Guid>();

            foreach (var ipData in auth.IpDatas)
            {
                if (string.IsNullOrEmpty(ipData.IpTo)) //only an ip not a range
                {
                    var intersections =
                        activeAccountRange.Where(a => a.RangeFinders.Any(c => c.InRange(IPAddress.Parse(ipData.IpFrom))))
                            .Select(a => a.Id)
                            .ToList();
                    var intersectionWithIp =
                        onlyIpData.Where(d => d.Ip.Any(c => c == ipData.IpFrom)).Select(a => a.Id).ToList();
                    list.AddRange(intersections);
                    list.AddRange(intersectionWithIp);
                }
                else //range ip 
                {

                    //check for range overlapping
                    var rangeIntersections =
                        activeAccountRange.Where(
                            a =>
                                a.RangeFinders.Any(
                                    d => d.Intersects(IPAddress.Parse(ipData.IpFrom), IPAddress.Parse(ipData.IpTo))))
                            .Select(a => a.Id)
                            .ToList();
                    var ipFind = new IpRangeFinder(IPAddress.Parse(ipData.IpFrom), IPAddress.Parse(ipData.IpTo));
                    foreach (var ip in onlyIpData) //check for ip data only
                    {
                        foreach (var realIp in ip.Ip)
                        {
                            if (ipFind.InRange(IPAddress.Parse(realIp)))
                            {
                                list.Add(ip.Id);
                            }
                        }
                    }
                    list.AddRange(rangeIntersections);
                }
            }
            list = list.Distinct().ToList();
            return activeAccountData.Where(a => list.Contains(a.Id)).ToArray();
        }

        public AccountIpAuthentication GetAccountFromIp(string ip)
        {
            var activeAccountData = _plainBl.ReadAllWhere(a =>  a.Status == Status.Active && a.IpDatas.Any());
            var activeAccountRange = activeAccountData.Select(a => new
            {
                a.Id,
                RangeFinders =
                    a.IpDatas.Where(c => !string.IsNullOrEmpty(c.IpTo))
                        .Select(d => new IpRangeFinder(IPAddress.Parse(d.IpFrom), IPAddress.Parse(d.IpTo)))
            }).ToList();
            var data= activeAccountRange.SingleOrDefault(a => a.RangeFinders.Any(d => d.InRange(IPAddress.Parse(ip))));
            if (data == null)
            {

                var id = activeAccountData.Select(d => new
                {
                    d.Id,
                    Ip = d.IpDatas.Where(a => !string.IsNullOrEmpty(a.IpFrom) && string.IsNullOrEmpty(a.IpTo)).Select(c=>c.IpFrom).ToList()
                });
                var datasingle = id.SingleOrDefault(a => a.Ip.Any(c => c.Equals(ip)));
                if (datasingle == null) return new AccountIpAuthentication();
                var dataFound=activeAccountData.FirstOrDefault(a => a.Id == datasingle.Id) ?? new AccountIpAuthentication();
                UpdateUsername(dataFound);
                return dataFound;
            }
            
            var dataFoundA=activeAccountData.FirstOrDefault(a => a.Id == data.Id);
            if (dataFoundA == null) return new AccountIpAuthentication();
                UpdateUsername(dataFoundA);
            return dataFoundA;
        }

        public bool DeleteIpData(Guid accountId)
        {
            return _plainBl.Delete(accountId);
        }

        public void RenameIpData(Guid accountId, string newAcoountName)
        {
            var accIpAuth = _plainBl.Read(accountId);
            if (accIpAuth != null)
            {
                accIpAuth.AccountName = newAcoountName;
                _plainBl.Update(accountId, accIpAuth);
            }
        }

        private void UpdateUsername(AccountIpAuthentication dataFound)
        {
            var userName = _plainBlUser.ReadAllWhere(a => a.Id == dataFound.UserId).FirstOrDefault();
            if (userName != null) dataFound.UserName = userName.Email;
        }
    }

}
